package com.xnx.bincker.leader.core.security;

import com.xnx.bincker.leader.dao.AuthorityRepository;
import com.xnx.bincker.leader.domain.Authority;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class SecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    private AuthorityRepository authorityRepository;

    public SecurityMetadataSource(AuthorityRepository authorityRepository) {
        this.authorityRepository = authorityRepository;
    }

    private static List<UrlConfigAttribute> urlConfigAttributes = Collections.emptyList();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        FilterInvocation filterInvocation = (FilterInvocation) o;
        HttpServletRequest request = filterInvocation.getRequest();
        if(urlConfigAttributes.isEmpty()) loadAllConfigAttributeMap();
        for (UrlConfigAttribute urlConfigAttribute : urlConfigAttributes) {
            if(urlConfigAttribute.matches(request)) return urlConfigAttribute.configAttributes;
        }
        return null;
    }

    synchronized private void loadAllConfigAttributeMap() {
        urlConfigAttributes = authorityRepository
                .findAll()
                .stream()
                .map(UrlConfigAttribute::new)
                .collect(Collectors.toList());
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        if(urlConfigAttributes.isEmpty()) loadAllConfigAttributeMap();
        return urlConfigAttributes.stream().reduce(new LinkedList<>(), (list, item)->{
            list.addAll(item.configAttributes);
            return list;
        }, (a,b)->a);
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

    private class UrlConfigAttribute{
        RequestMappingInfo requestMappingInfo;
        Collection<ConfigAttribute> configAttributes;

        UrlConfigAttribute(Authority authority) {
            Authority.MappingInfo mappingInfo = authority.getMappingInfo();
            this.requestMappingInfo = RequestMappingInfo
                    .paths(mappingInfo.getPath().toArray(new String[]{}))
                    .methods(mappingInfo.getMethod().toArray(new RequestMethod[]{}))
                    .produces(mappingInfo.getProduces().toArray(new String[]{}))
                    .build();
            this.configAttributes = authority
                    .getRoles()
                    .stream()
                    .map(item->new SecurityConfig(item.getCode()))
                    .collect(Collectors.toList());
        }

        boolean matches(HttpServletRequest request){
            return requestMappingInfo.getMatchingCondition(request) != null;
        }
    }
}
